package serverManger;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import message.FileWasChanged;
import message.Info;
import message.Message;
import message.NewBox;
import search.TagedFiles;
import serverDB.Box;
import serverDB.Client;
import serverDB.Computer;
import serverDB.ConnectToDataBase;
import serverDB.File;
import tray.TrayIcon;
import webService.StartWebService;
import Watcher.ChangeType;
import connections.AcceptSocket;
import connections.JettyManager;
import connections.SendMailSSL;
/**
 * this class managing all request that arrive to the  
 * @author ran
 *
 */
public class Server {
	private static Server serverInst= new Server(null);
	private ConnectToDataBase connectToDataBase;//=new ConnectToDataBase();
	private JettyManager jettyManager;
	private String location;
	private int fileNameCounter;
	private HashMap<String,ArrayList<Box>> acceptRequestForSharing;	
	private HashMap<Long,Boolean> isConnected;
	private HashMap<Long,CompMessages> compUpdates;
	private StartWebService startWebService;
	private AcceptSocket acceptSocket;
	/**
	 * 
	 * @return instance of the class
	 */
	public static Server getServerInst(){
		return serverInst;
	}
	/**
	 * set the member jettyManager
	 * @param jettyManager the new value of the member
	 */
	public void setJettyManager(JettyManager jettyManager) {
		this.jettyManager = jettyManager;
	}
	/**
	 * constructor of the class
	 * @param jettyManager
	 */
	private Server(JettyManager jettyManager) {
		this.jettyManager = jettyManager;
		connectToDataBase=ConnectToDataBase.getInst();
		location=ServerInfo.serverInfoIns.getFolderLocation();
		fileNameCounter=0;
		isConnected = new HashMap<Long, Boolean>();
		this.compUpdates=new HashMap<Long, CompMessages>();
		acceptRequestForSharing=new HashMap<String, ArrayList<Box>>();
	}
	/**
	 * start the server
	 */
	public void start(){
		acceptSocket = new AcceptSocket();
		Thread thread = new Thread(acceptSocket);
		thread.start();
		startWebService = new StartWebService();
		Thread webService = new Thread(startWebService);
		webService.start();
		// initiate new message list for all the user in the DB
		List<Client> clients = connectToDataBase.getClient2("from Client");
		if(clients !=null){
			for(Client c : clients){
				Set<Computer> comps = c.getComputers();
				for(Computer comp : comps){
					compUpdates.put(comp.getComputerId(), new CompMessages());
					isConnected.put(comp.getComputerId(), true);
				}
			}
			
		}
		TrayIcon trayIcon = new TrayIcon();
		Thread tray = new Thread(trayIcon);
		tray.start();
	
	}
	/**
	 * add new client to the server
	 * @param username the user name of client
	 * @param pass the client password
	 * @param email the client email
	 * @param computerName the client computer name
	 * @param path the location of the drop box location
	 * @param boxName the folder name of client
	 * @return message to send to the client
	 */
	public Message addNewClient(String username, String pass, String email,
			String computerName, String path, String boxName) {
		//create new client in DB
		Client client = connectToDataBase.createNewClient(username, pass, email, computerName, path, boxName);
		Set<Computer> computers = client.getComputers();
		System.out.println("size: "+computers.size());
		Computer theComp=null;
		//runs on all the computer of this clients
		for(Computer c : computers){
			System.out.println("name: "+ c.getComputerName()+"  "+computerName);
			if(computerName.compareTo(c.getComputerName())==0){
				theComp=c;
				break;
			}
		}
		System.out.println("after loop: "+theComp.getComputerId());
		CompMessages compMessage = new CompMessages();
		getAllFiles(client,compMessage);
		System.out.println("8374928479287#@$@#$@#$");
		System.out.println("size of compMessage : "+compMessage.getSize());
		compUpdates.put(theComp.getComputerId(), compMessage);
		//new client
		if(boxName!=null){
			//send welcome message to the fresh client
			SendMailSSL mailSSL = new SendMailSSL();
			mailSSL.sendMail("Welcome to CleverDropBox", "Thank you for using CleverDropBox we hope you will enjoy the service...\n"
					+"your user name is: "+username+"\nyour password is: "+pass+"\nyour email is: "
					+email+"\nwith thanks\nCleverDropBox team", email);
		}
		Set<String>boxesName=new HashSet<String>();
		Set<Box> boxes = client.getBoxes();
		for(Box b : boxes)
			boxesName.add(b.getBoxName());
		//return info message to send to the client 
		return new Info(client.getUserName(),client.getPassword(),client.getUserEmail(),boxesName);
		
	}
	/*
	 * send all files that in client computer to the new computer that add now
	 */
	private void getAllFiles(Client client, CompMessages compMessage) {
		Set<Box> boxes = client.getBoxes();
		for(Box b : boxes){
			Set<File> files = b.getFiles();
			for(File f : files){
				compMessage.pushMessage(new FileWasChanged(f.getFileName(), f.getCheckSum(), 0, b.getBoxName(), f.getModefiedDate(),null,ChangeType.CREATE));
			}
		}
	}

	/**
	 * update if client is connected to server or not
	 * @param computerId
	 * @param isConnected
	 */
	public void setClientIsConnected(Long computerId,boolean isConnected){
		this.isConnected.put(computerId, isConnected);
	}
	/**
	 * check if the client exist in server already
	 * @param userName the client user name
	 * @param pass the client password
	 * @return the client instance is exist
	 */
	public Client isClientExist(String userName, String pass) {
		Client client=connectToDataBase.getClient(userName,pass);
		if(client!=null)
			return client;
		return null;
		
		
	}
	
	/**
	 * check if file exist in server already
	 * @param checkSum the file identifier
	 * @return file instance if exist
	 */
	public List<File> isFileExist(long checkSum) {
		List<File> files=connectToDataBase.getFile(checkSum);
		if(files!=null)
			return files;
		return null;
	}
	/**
	 * deal with change in file message from client
	 * @param user
	 * @param fileName
	 * @param checkSum
	 * @param size
	 * @param boxName
	 * @param comp
	 * @param fileWasChanged
	 * @return success or failure
	 */
	public boolean fileWasChanged(Client user,String fileName,long checkSum, long size, String boxName, Computer comp,FileWasChanged fileWasChanged){
		System.out.println("fileWasChanged :");
		System.out.println("fileName : "+fileName+"\ncheckSum : "+checkSum+"\nsize : "+size+"\nboxName : "+boxName);
		//check if file already exist
		List<File> files=isFileExist(checkSum);
		boolean fileFound=false;
		//return the folder of the client that contain the file that change
		Box box = connectToDataBase.getBox(user, boxName);
		//get all files in the folder
		Set<File> filesInBox=box.getFiles();
		File file=null;
		//runs over all the files and get the file that change
		for(File f : filesInBox){
			if(f.getFileName().compareTo(fileName)==0){
				fileFound=true;
				file=f;	
				break;
			}
		}
		
		System.out.println("345345083759384759834759");
		// if the file is already exist in the server by the checkSum
		if(files!=null ){
			//file isn`t exist in the client box by the name and the request of the client is not rename
			if(!fileFound&&fileWasChanged.getChangeType()!=ChangeType.RENAME){
				System.out.println("HOME SWEET HOME!!!");
				//create new file in DB
				connectToDataBase.createFile(fileName, files.get(0).getFileLocation(), files.get(0).getCheckSum(),box,fileWasChanged.getModDate(),fileWasChanged.getAllTages());
				//update other clients in change
				updateOtherCompMess(user, comp, fileWasChanged,boxName);
			}
			//the request of the client is rename
			else if(fileWasChanged.getChangeType()==ChangeType.RENAME){
				for(File f : files){
					if(f.getBoxes().contains(box)){
						file=f;
						break;
					}
				}
				//file not found
				if(file==null){
					connectToDataBase.createFile(fileName, files.get(0).getFileLocation(), files.get(0).getCheckSum(),box,fileWasChanged.getModDate(),fileWasChanged.getAllTages());
					updateOtherCompMess(user, comp, fileWasChanged,boxName);
				}
				else{
					connectToDataBase.renameFile(file,fileWasChanged.getFileName());
					updateOtherCompMess(user, comp, fileWasChanged,boxName);
				}
			}
			else{
				connectToDataBase.changeTags(file,fileWasChanged.getAllTages());
			}
			return false;
		}
		//file isn`t exist in server
		else{
			// file found in the client box => modified/delete file
			if(fileFound){
				System.out.println("FileFound");
				if(connectToDataBase.deleteFile(file)){
					System.out.println("delete file : "+file.getFileLocation());
					java.io.File fileToDel=new java.io.File(file.getFileLocation());
					fileToDel.delete();
				}
			}	
			// delete file message return stopComunication
			if(checkSum==-1){
				updateOtherCompMess(user, comp, fileWasChanged,boxName);
				return false;
			}
		}
		return true;
	}
	/**
	 * 
	 * @param fileName name of the file
	 * @return the data in the file
	 */
	public byte[] getFileBytes(String fileName){
		byte [] byteArray=null;
		try {
			java.io.File file = new java.io.File (fileName);
			byteArray  = new byte [(int)file.length()];
			FileInputStream fis = new FileInputStream(file);
			BufferedInputStream bis = new BufferedInputStream(fis);
			bis.read(byteArray);
			bis.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return byteArray;
	}
	/**
	 * create new file in server
	 * @param fileName the name of the file
	 * @param byteArray the data in the file
	 * @param boxName folder name
	 * @param user the client instance that owns the file
	 * @param checkSum identifier of the file
	 * @param modDate last modification date of file
	 * @param tagedFiles the tags of file
	 * @return the File that created (DB instance)
	 */
	public File createFile(String fileName, byte[] byteArray, String boxName, Client user, Long checkSum,Date modDate,ArrayList<TagedFiles> tagedFiles){ 
		System.out.println("printing filename : "+fileName);
		FileOutputStream fos;
		try {
			//create file in file system of server
			fos = new FileOutputStream(location+"\\"+fileNameCounter);
			BufferedOutputStream bos = new BufferedOutputStream(fos);
			bos.write(byteArray);
			bos.flush();
			bos.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Set<Box> boxes = user.getBoxes();
		Box box=null;
		System.out.println("size:"+boxes.size());
		//runs over all boxes that contain the file
		for(Box b : boxes){
			System.out.println(boxName);
			if(b==null)
				System.out.println("WTF????");
			System.out.println("current box name: "+b.getBoxName());
			//found the folder
			if(b.getBoxName().compareTo(boxName)==0){
				System.out.println("found the box");
				box=b;
				break;
			}
		}
		//create file in DB
		File file = connectToDataBase.createFile(fileName,location+"\\"+fileNameCounter,checkSum,box,modDate,tagedFiles);
		fileNameCounter++;
		return file;
	}

	/*private Long getChecksum(String fileName){
		try {

            CheckedInputStream cis = null;
            //long fileSize = 0;
            // Computer CRC32 checksum
            cis = new CheckedInputStream(new FileInputStream(fileName), new CRC32());
            //fileSize = new File(fileName).length();

            byte[] buf = new byte[128];
            while(cis.read(buf) >= 0) {
            }

            return cis.getChecksum().getValue();            

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        return (long) -1;
	}*/
	/**
	 * check if client is connected
	 */
	private boolean isConnected(long computerId){
		return isConnected.get(computerId);
	}
	/**
	 * update other computer in changes in file
	 * @param user client that change something
	 * @param comp computer instance of the change computer
	 * @param file file that change
	 */
	public void updateOtherComp(Client user, Computer comp, File file) {

		Set<Computer> computers = user.getComputers();
		Set<Box> compBoxes = user.getBoxes();
		Set<Box> fileBoxes = file.getBoxes();
		String boxName = null;
		//runs over all folders of user
		for(Box b : compBoxes){
			//runs over all folders that connect to the file that change
			for(Box fileBox : fileBoxes)
				//the box found!!!
				if(b.getBoxId()==fileBox.getBoxId()){
					boxName=b.getBoxName();
					break;
				}
			if(boxName!=null)
				break;
		}
		//runs over the computer and send message if the computer is not connected
		for(Computer c : computers){
			if(comp==null || (c.getComputerId()!=comp.getComputerId() && !isConnected(c.getComputerId()))){
				//start communication
				//connectToDataBase.addUpdate(c,file);
				CompMessages compMessages = compUpdates.get(c.getComputerId());
				long fileSize = new java.io.File(file.getFileLocation()).length();
				compMessages.pushMessage(new FileWasChanged(file.getFileName(), file.getCheckSum(),fileSize,boxName,file.getModefiedDate(),null,ChangeType.CREATE));
				//continue to the client conversation
				jettyManager.resumeComp(c.getComputerId());
			}
		}
	}

	/**
	 * update other computer in new message
	 * @param client client that change something
	 * @param comp computer instance of the change computer
	 * @param message the message to send to other computers
	 * @param boxName the folder name that change
	 */
	public void updateOtherCompMess(Client client, Computer comp, Message message, String boxName) {
		//connectToDataBase.addUpdate(comp,file);
		Box box = connectToDataBase.getBox(client, boxName);
		Set<Client> clients = box.getclients();
		//runs over all clients that share that folder
		for(Client user : clients){
			Set<Computer> computers = user.getComputers();
			//runs over all the computer of the client
			for(Computer c : computers){
				//computer found
				if(comp==null || c.getComputerId()!=comp.getComputerId()){
					//start communication
					//connectToDataBase.addUpdate(c,file);
					CompMessages compMessages = compUpdates.get(c.getComputerId());
					compMessages.pushMessage(message);	
					//the computer is nor connected
					if(!isConnected(c.getComputerId())){
						jettyManager.resumeComp(c.getComputerId());
					}
				}

			}
		}
	}
	/**
	 * 
	 * @param comp the computer instance
	 * @return the next message to certain computer
	 */
	public Message getNextMessage(Computer comp) {
		if(comp==null)
			return null;
		CompMessages compMessages = compUpdates.get(comp.getComputerId());
		return compMessages.getNextMessage();
	}
	/**
	 * stop syncronize with computer
	 * @param comp the computer instance
	 */
	public void stopSync(Computer comp) {
		jettyManager.removeComp(comp.getComputerId());

	}
	/**
	 * create new folder in data base
	 * @param boxName the name of the folder
	 * @param user client that send the message
	 * @param comp computer that send message
	 */
	public void newBox(String boxName, Client user, Computer comp) {
		connectToDataBase.newBox(boxName,user);
		updateOtherCompMess(user, comp, new NewBox(boxName),boxName);

	}
	/**
	 * deal with share folder message
	 * @param user client that send the message
	 * @param boxName the name of the folder
	 * @param emails list of emails of clients the client want to share the folder with 
	 */
	public void shareBox(Client user, String boxName, ArrayList<String> emails) {
		SendMailSSL sendingOpr=new SendMailSSL();
		Set<Box> boxes=user.getBoxes();
		String host="localhost";
		List<Client> clients=null;
		Box theBox=null;
		//find the box
		for(Box box: boxes){
			if(box.getBoxName().compareTo(boxName)==0){
				theBox=box;
				break;
			}
		}
		//send emails to the clients 
		for(String address : emails){
			ArrayList<Box> boxesList = acceptRequestForSharing.get(address);
			if(boxesList==null){
				boxesList = new ArrayList<Box>();
				acceptRequestForSharing.put(address, boxesList);
			}
			boxesList.add(theBox);
			clients=connectToDataBase.getClient2("from Client where email='"+address+"'");
			if(clients!=null){
				sendingOpr.sendMail("new shared folder", user.getUserName()+" want to share the folder: "+boxName+" with you...\nTo Accept click on the link below:\nhttp://"+host+"/AcceptSharedFolder?email="+address, address);
			}else{
				sendingOpr.sendMail("new shared folder", user.getUserName()+" want to share the folder: "+boxName+" with you...\nTo download cleverDropBox click on the link below:\nhttp://"+host, address);

			}

		}

	}
	/**
	 * send message to client that other client want to share folder with him
	 * @param email
	 */
	public void acceptSharedFolder(String email) {
		System.out.println("user Email : "+email);
		ArrayList<Box> boxes = acceptRequestForSharing.remove(email);
		if(boxes==null)
			System.out.println("ldfukjahfishrkjszhkjsdkfh");

		List<Client> clients = ConnectToDataBase.getInst().getClient2("from Client where email='"+email+"'");
		for(Client c : clients){
			Set<Computer> comps = c.getComputers();
			for(Box box : boxes){
				connectToDataBase.addBoxToClient(c,box);
				for(Computer comp : comps){
					CompMessages compMessages = compUpdates.get(comp.getComputerId());
					compMessages.pushMessage(new NewBox(box.getBoxName()));
					if(!isConnected(comp.getComputerId())){
						jettyManager.resumeComp(comp.getComputerId());
					}
				}
			}
		}
	}
	/**
	 * return info message with information about client
	 * @param user the client instance
	 * @return Info message
	 */
	public Message getClientInfo(Client user) {
		Set<Box> boxes = user.getBoxes();

		Set<String>boxesName=new HashSet<String>();
		for(Box b : boxes)
			boxesName.add(b.getBoxName());
		return new Info(user.getUserName(), user.getPassword(), user.getUserEmail(), boxesName);
	}


}
